<html>
    <head>
        <title>数据播种</title>
    </head>
    <body>
        <script>
            // 数据播种  Data Seeding

                    /*
                        一、导言

                                使用数据库的某些应用程序(或模块)，可能需要一些"初始数据" 才能正确启动和运行。

                                例如，管理用户 和 角色 必须在开始时可用。否则，你无法登录到应用程序以创建新用户和角色。

                                数据播种对于测试也很有用，因此你的自动测试可以假设数据库有一些可用的初始数据。
                    */

                    /*
                        二、为什么使用数据播种系统？

                                虽然EF Core数据播种系统提供了一种方法，但它非常有限，不涵盖生产场景。此外，它仅适用于EF Core。


                                ABP提供了一个数据种子系统，即：

                                    ①、模块化：  
                                                任何模块都可以默默地为数据播种过程做出贡献，而无需相互了解和影响。
                                                
                                                这样，每个模块播种自己的初始数据。

                                    ②、数据库独立：
                                    
                                                它不仅适用于EF Core，还适用于其他数据库提供程序（如MongoDB）。

                                    ③、生产就绪：
                                    
                                                它解决了生产环境中的问题。请参阅下面的“生产”部分。

                                    ④、依赖注入：
                                    
                                                它充分利用了依赖注入，因此你可以在播种初始数据时使用任何内部或外部服务。实际上，你可以做的不仅仅是数据播种。

                    */

                    /*
                        三、数据播种逻辑  IDataSeedContributor 接口

                                IDataSeedContributor 接口定义了数据播种逻辑，是为了向数据库播种数据而应该实现的接口。

                                eg:  如果没有书，则向数据库播种一本书：

                                        using System;
                                        using System.Threading.Tasks;
                                        using Volo.Abp.Data;
                                        using Volo.Abp.DependencyInjection;
                                        using Volo.Abp.Domain.Repositories;
                                        using Volo.Abp.Guids;

                                        namespace Acme.BookStore
                                        {
                                            // 数据播种贡献者类，用于向数据库播种数据。
                                            public class BookStoreDataSeedContributor: IDataSeedContributor, ITransientDependency
                                            {
                                                private readonly IRepository<Book, Guid> _bookRepository; // book存储
                                                private readonly IGuidGenerator _guidGenerator; // 生成可排序Guid
                                                private readonly ICurrentTenant _currentTenant; // 档期租户

                                                public BookStoreDataSeedContributor(
                                                    IRepository<Book, Guid> bookRepository,
                                                    IGuidGenerator guidGenerator,
                                                    ICurrentTenant currentTenant)
                                                {
                                                    _bookRepository = bookRepository;
                                                    _guidGenerator = guidGenerator;
                                                    _currentTenant = currentTenant;
                                                }
                                                
                                                //  播种方法
                                                public async Task SeedAsync(DataSeedContext context)
                                                {
                                                    // 切换到当前租户（如果有）
                                                    using (_currentTenant.Change(context?.TenantId))
                                                    {
                                                        if (await _bookRepository.GetCountAsync() > 0)
                                                        {
                                                            return;
                                                        }

                                                        var book = new Book(
                                                            id: _guidGenerator.Create(),
                                                            name: "The Hitchhiker's Guide to the Galaxy",
                                                            type: BookType.ScienceFiction,
                                                            publishDate: new DateTime(1979, 10, 12),
                                                            price: 42
                                                        );

                                                        await _bookRepository.InsertAsync(book);
                                                    }
                                                }
                                            }
                                        }

                                IDataSeedContributor定义了SeedAsync方法来执行数据种子逻辑。

                                通常ABP会检查数据库中种子是否已经存在。

                                你可以注入任何服务，并执行播种数据所需的任何逻辑。


                                ★、数据种子上下文  DataSeedContext

                                        DataSeedContext包含TenantId，因此你可以在插入数据或基于租户执行自定义逻辑时使用此值。

                                        DataSeedContext还包含名称-值样式配置参数，用于从IDataSeeder传递给播种机贡献者。
                    */

                    /*
                        四、模块化

                                一个应用程序可以有多个数据种子贡献者（IDataSeedContributor）类。


                                因此，任何可重用模块也可以实现此接口来播种自己的初始数据。


                                例如，身份模块有一个数据种子贡献者，它创建一个管理员角色和管理员用户并分配所有权限。
                    */

                    /*
                        五、IDataSeeder  数据播种引擎服务   

                                你通常不需要使用 IDataSeeder服务，因为如果你已经使用了 "启动模板"，它就已经完成了。

                                但是建议你阅读当前文档以了解数据种子系统背后的设计。

                                IDataSeeder 服务是用于播种初始数据的主要服务。 它的使用很简单：

                                                public class MyService : ITransientDependency
                                                {
                                                    private readonly IDataSeeder _dataSeeder;

                                                    public MyService(IDataSeeder dataSeeder)
                                                    {
                                                        _dataSeeder = dataSeeder; // 注入数据播种引擎服务
                                                    }

                                                    public async Task FooAsync()
                                                    {
                                                        await _dataSeeder.SeedAsync(); // 执行数据播种逻辑

                                                    }
                                                }

                                你可以注入IDataSeeder并在需要时使用它来播种初始数据。

                                它在内部调用所有 IDataseeder的实现来完成数据播种。



                                1、使用数据播种上下文(DataSeedContext)传递配置参数
                            
                                        你还可以通过数据播种上下文 DataSeedContext 来讲配置参数传递给数据播种程序(DataSeedContributor)

                                            eg:
                                                await _dataSeeder.SeedAsync(
                                                    new DataSeedContext()
                                                        .WithProperty("MyProperty1", "MyValue1")
                                                        .WithProperty("MyProperty2", 42)
                                                );

                                        然后数据播种程序(DataSeedContributor)可以在SeedAsync方法中使用这些配置参数。


                                        如果一个模块的数据播种操作需要配置参数，那它的文档中应该声明。

                                        例如，如果你提供 IDentity 模块可以使用 AdminEmail 和 AdminPassword参数来执行默认账户的邮箱和密码（否则使用框架默认的密码）。


                                2、使用独立的播种工作单元 SeedInSeptarateUowAsync() 方法 

                                        默认种子将在一个工作单元中，可能会使用事务。

                                        如果有多个 IDataSeedContributor 或者写入的数据过多，可能会导致"数据库超时错误"。

                                        我们为 IDataSeedContributor 服务提供了 SeedInSeptarateUowAsync (在独立的工作单元中播种) 方法。
                                        为每个 IDataSeedContributor 创建一个单独的工作单元：

                                            public static Task SeedInSeparateUowAsync(this IDataSeeder seeder, Guid? tenantId = null, AbpUnitOfWorkOptions options = null, bool requiresNew = false)


                                3、在那里播种？ 如何播种?

                                        应用程序启动模板附带一个YourProjectName**. DbMigrator**项目（下图中的Acme.BookStore.DbMigrator），

                                        它是一个控制台应用程序，负责迁移数据库模型（用于关系数据库）和种子初始数据：

                                        此控制台应用程序已为你正确配置。它甚至支持多租户场景，其中每个租户都有自己的数据库（迁移和种子所有必要的数据库）。

                                        每当你将解决方案的新版本部署到服务器时，预计都会运行此DbMigrator应用程序。

                                        它将迁移你的数据库模式（创建新表/字段…等）并播种正确运行新版本解决方案所需的新初始数据。

                                        然后你可以部署/启动你的实际应用程序。

                                        即使你正在使用MongoDB或其他非关系型数据库（不需要模式迁移），也建议使用DbMigrator应用程序来播种数据或执行数据迁移。



                                        拥有这样一个单独的控制台应用程序有几个优点:

                                                ①、你可以在更新你的应用程序之前运行它，因此你的应用程序将在就绪的数据库上运行。

                                                ②、与播种初始数据本身相比，你的应用程序启动速度更快。

                                                ③、你的应用程序可以在集群环境上正确运行（你的应用程序的多个实例同时运行）。如果你在应用程序启动时播种数据，在这种情况下你会遇到冲突。

                                4、开发时的注意点
                                
                                        我们建议在开发中使用相同的方法。

                                        每当你创建数据库迁移时(例如，使用EF Core Add-Migration命令)  或更改数据种子代码(稍后将解释)时，运行DbMigrator应用程序。
                                        (你可以继续使用EF Core 的标准 Update-Database命令，但是如果你创建了新的种子数据，它将不会播种)。

                                5、关于测试

                                        你可能还希望为自动化测试播种数据，因此希望使用IDataSeeder.SeedAsync()。
                                        
                                        在应用程序启动模板中，它是在TestBase项目的YourProjectNameTestBaseModule类的OnApplicationInitialization方法中完成的。

                                        除了标准种子数据（也用于生产）之外，你可能希望为自动化测试独有的其他数据播种。
                                        
                                        如果是这样，你可以在测试项目中创建一个新的数据种子贡献者，以处理更多数据。
                    */


        </script>
    </body>
</html>